﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Resources;
using System.Reflection.Emit;
using System.IO;
using System.Globalization;
using ArgusLib;
using ArgusLib.Reflection;
using ArgusLib.Zip;

namespace Localysator
{
	partial class MainForm : ArgusLib.Controls.BaseForm
	{
		Assembly mainAssembly;
		Dictionary<string, DataGridViewRow> dictRows;
		ResourceInfo currentResource;
		CultureInfo currentCulture;
		TempDirectory tempDir;
		int isInitializingCells = 0;

		public MainForm()
			: base()
		{
			InitializeComponent();
		}

		bool CanSave
		{
			get { return this.tsmiMenuFileSave.Enabled; }
			set { this.tsmiMenuFileSave.Enabled = value; this.tsbSave.Enabled = value; }
		}

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			this.Text = this.Text + " v" + ArgusLib.Environment.GetAssemblyVersion().ToString();
			this.cbCultures.SelectedItem = CultureInfo.InvariantCulture;
			Application.ApplicationExit += new EventHandler((sender, eventArgs) =>
				{
					if (this.tempDir != null)
						this.tempDir.Dispose();
				});
			this.tempDir = TempDirectory.Create();
		}

		protected override void OnShown(EventArgs e)
		{
			base.OnShown(e);
			this.tsmiMenuHelpUpdates_Click(null, null);
		}

		private void tsmiMenuFileOpen_Click(object sender, EventArgs e)
		{
			if (this.ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK)
				return;
			try { this.mainAssembly = Assembly.ReflectionOnlyLoadFrom(this.ofd.FileName); }
			catch 
			{
				ArgusLib.Controls.MessageBox.Show("Invalid File.", "Error");
				this.pRoot.Enabled = false;
				this.mainAssembly = null;
				return;
			}
			this.pRoot.Enabled = true;
			this.CanSave = false;
			Assembly[] assemblies = AssemblyReflector.GetReferencedAssemblies(this.mainAssembly, false, true, true);
			this.cbResources.BeginUpdate();
			this.cbResources.Items.Clear();
			foreach (Assembly a in assemblies)
			{
				string[] res = a.GetManifestResourceNames();
				foreach (string s in res)
					this.cbResources.Items.Add(new ResourceInfo(s, a));
			}
			this.cbResources.EndUpdate();
			this.cbResources.PerformLayout();
			if (this.cbResources.Items.Count > 0)
			{
				this.cbResources.SelectedIndex = 0;
				this.currentResource = (ResourceInfo)this.cbResources.SelectedItem;
			}
			this.cbCultures.SelectedItem = this.mainAssembly.GetName().CultureInfo;
			this.currentCulture = (CultureInfo)this.cbCultures.SelectedItem;
		}

		private void cbResources_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (this.cbResources.SelectedIndex < 0)
				return;

			this.SaveToTempDir();

			this.currentResource = (ResourceInfo)this.cbResources.SelectedItem;
			Dictionary<string, string> resStrings = AssemblyReflector.GetStringResources(this.currentResource.Assembly, this.currentResource.Name, null);
			this.isInitializingCells++;
			this.dgvValues.Rows.Clear();
			this.dictRows = new Dictionary<string, DataGridViewRow>(resStrings.Count);
			foreach(KeyValuePair<string,string> res in resStrings)
			{
				if (res.Key.StartsWith(">>"))
					continue;

				this.dgvValues.Rows.Add(res.Key, res.Value);
				DataGridViewRow row = this.dgvValues.Rows[this.dgvValues.Rows.Count-1];
				row.Cells[1].ToolTipText = res.Value;
				this.dictRows.Add(res.Key, row);
			}
			this.isInitializingCells--;

			this.currentCulture = CultureInfo.InvariantCulture;
			if (!this.cbCultures.SelectedItem.Equals(CultureInfo.InvariantCulture))
				this.cbCultures_SelectedIndexChanged(this.cbCultures, EventArgs.Empty);
		}

		private void cbCultures_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (this.cbCultures.SelectedIndex < 0)
			{
				this.dgvValues.Enabled = false;
				return;
			}

			this.dgvValues.Enabled = !this.cbCultures.SelectedItem.Equals(CultureInfo.InvariantCulture);

			if (this.dictRows == null|| this.currentResource == null)
				return;

			this.SaveToTempDir();

			this.isInitializingCells++;
			foreach (DataGridViewRow row in this.dgvValues.Rows)
				row.Cells[1].Value = null;

			this.currentCulture = (CultureInfo)this.cbCultures.SelectedItem;
			Dictionary<string, string> resStrings = AssemblyReflector.GetStringResources(this.currentResource.Assembly, this.currentResource.Name, this.currentCulture, false);
			if (resStrings == null)
			{
				this.isInitializingCells--;
				return;
			}

			foreach (KeyValuePair<string, string> res in resStrings)
			{
				if (res.Key.StartsWith(">>"))
					continue;

				DataGridViewRow row;
				if (this.dictRows.TryGetValue(res.Key, out row) == false)
					continue;
				row.Cells[1].Value = res.Value;
			}
			this.isInitializingCells--;

			this.LoadFromTempDir();
		}

		FileInfo GetTempResFile(bool createDir)
		{
			if (this.currentCulture == null || this.currentCulture.Equals(CultureInfo.InvariantCulture) || this.currentResource == null)
				return null;
			return this.GetTempResFile(this.currentResource.Name, this.currentCulture.Name, createDir);
		}

		FileInfo GetTempResFile(string resourceName, string cultureName, bool createDir)
		{
			DirectoryInfo dir = this.tempDir.DirectoryInfo.CombineDirectory(resourceName);
			if (!dir.Exists && !createDir)
				return null;
			dir.Create();
			string[] split = resourceName.Split('.');
			StringBuilder filename = new StringBuilder();
			if (split.Length > 1)
				filename.Append(split[split.Length - 2]);
			else
				filename.Append(split[0]);
			filename.Append('.');
			filename.Append(cultureName);
			filename.Append(".resx");
			return dir.CombineFile(filename.ToString());
		}

		void GetInfoFromTempResFile(FileInfo resFile, out string resourceName, out string cultureName)
		{
			resourceName = Path.GetFileName(resFile.DirectoryName);
			string[] split = resFile.Name.Split('.');
			if (split.Length < 2)
				cultureName = string.Empty;
			else
				cultureName = split[split.Length - 2];
		}

		void SaveToTempDir()
		{
			if (this.currentCulture == null || this.currentCulture.Equals(CultureInfo.InvariantCulture) || this.currentResource == null)
				return;
			FileInfo file = this.GetTempResFile(true);
			if (file == null)
				return;

			using (Stream stream = file.Open(FileMode.Create, FileAccess.ReadWrite))
			{
				ResXResourceWriter writer = new ResXResourceWriter(stream);
				foreach(DataGridViewRow row in this.dgvValues.Rows)
				{
					string key = row.Cells[0].Value as string;
					string value = row.Cells[1].Value as string;
					if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value))
						continue;
					writer.AddResource(key, value);
				}
				writer.Generate();
				writer.Close();
			}
		}

		void LoadFromTempDir()
		{
			if (this.currentCulture == null || this.currentCulture.Equals(CultureInfo.InvariantCulture) || this.currentResource == null)
				return;

			FileInfo file = this.GetTempResFile(false);
			if (file == null || !file.Exists)
				return;

			using(Stream stream = file.OpenRead())
			{
				this.isInitializingCells++;
				ResXResourceReader reader = new ResXResourceReader(stream);
				IDictionaryEnumerator enumerator = reader.GetEnumerator();
				while (enumerator.MoveNext())
				{
					string key = enumerator.Key as string;
					DataGridViewRow row;
					if (this.dictRows.TryGetValue(key, out row) == false)
						continue;

					row.Cells[1].Value = enumerator.Value;
				}
				this.isInitializingCells--;
			}
		}

		private void dgvValues_CellValueChanged(object sender, DataGridViewCellEventArgs e)
		{
			if (e.ColumnIndex < 0 || e.RowIndex < 0 || this.isInitializingCells > 0)
				return;

			this.CanSave = true;
		}

		private void tsmiMenuFileSave_Click(object sender, EventArgs e)
		{
			if (this.sfd.ShowDialog() != System.Windows.Forms.DialogResult.OK)
				return;

			this.SaveToTempDir();

			if (this.sfd.FilterIndex == 0)
				this.SaveAsResXFiles(this.sfd.FileName);
			else
				this.SaveAsAssemblies(this.sfd.FileName);
		}

		void SaveAsResXFiles(string filename)
		{
			using (FileStream stream = new FileStream(filename, FileMode.Create))
			{
				using (ZipFile zipFile = new ZipFile())
				{
					zipFile.CompressionLevel = ArgusLib.Zlib.CompressionLevel.BestCompression;
					zipFile.AddDirectory(this.tempDir.Path);
					zipFile.Save(stream);
				}
			}
		}

		void SaveAsAssemblies(string filename)
		{
			using (TempDirectory assemblyDir = TempDirectory.Create())
			{
				FileInfo[] files = this.tempDir.DirectoryInfo.GetFiles("*", SearchOption.AllDirectories);
				Dictionary<string, List<Assembly>> dictAssemblies = new Dictionary<string, List<Assembly>>();
				Dictionary<string, FileInfo> dictResFiles = new Dictionary<string, FileInfo>();
				foreach (FileInfo file in files)
				{
					string resName, cultName;
					this.GetInfoFromTempResFile(file, out resName, out cultName);
					dictResFiles.Add(resName, file);
					List<Assembly> assemblies;
					if (dictAssemblies.TryGetValue(cultName, out assemblies) == false)
					{
						assemblies = new List<Assembly>();
						dictAssemblies.Add(cultName, assemblies);
					}

					Assembly keyAssembly = this.GetAssemblyFromResourceName(resName);
					if (!assemblies.Contains(keyAssembly))
						assemblies.Add(keyAssembly);
				}
				files = null;

				bool? overwrite = null;

				foreach (KeyValuePair<string, List<Assembly>> cultureAndList in dictAssemblies)
				{
					DirectoryInfo dir = assemblyDir.GetSubdirectory(cultureAndList.Key);

					foreach (Assembly assembly in cultureAndList.Value)
					{
						AssemblyName aName = assembly.GetName();
						aName.CultureInfo = new CultureInfo(cultureAndList.Key);
						aName.Name += ".resources";
						FileInfo aFile = dir.CombineFile(aName.Name + ".dll");

						if (aFile.Exists)
						{
							if (overwrite == null && ArgusLib.Controls.MessageBox.Show("Overwrite existing files?", "Overwrite?",
								ArgusLib.Controls.MessageBox.Buttons.Yes | ArgusLib.Controls.MessageBox.Buttons.No) == System.Windows.Forms.DialogResult.Yes)
							{
								overwrite = true;
							}
							else
							{
								overwrite = false;
							}

							if (overwrite == false)
								continue;
							else
								aFile.Delete();
						}

						AssemblyBuilder aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Save, dir.FullName);

						string[] resNames = assembly.GetManifestResourceNames();

						foreach (string resName in resNames)
						{
							string mName = this.GetModuleName(resName, cultureAndList.Key);
							Dictionary<string, object> resources = AssemblyReflector.GetResources(assembly, resName, aName.CultureInfo, false);
							if (resources == null)
								resources = new Dictionary<string, object>();
							
							FileInfo file;
							if (dictResFiles.TryGetValue(resName, out file) == true)
							{
								using (Stream stream = file.OpenRead())
								{
									ResXResourceReader reader = new ResXResourceReader(stream);
									IDictionaryEnumerator enumerator = reader.GetEnumerator();
									while (enumerator.MoveNext())
										resources[(string)enumerator.Key] = enumerator.Value;
								}
							}
							ModuleBuilder mBuilder = aBuilder.DefineDynamicModule(mName, resName + ".netmodule");
							IResourceWriter rWriter = mBuilder.DefineResource(mName, string.Empty);
							foreach (KeyValuePair<string, object> item in resources)
								rWriter.AddResource(item.Key, item.Value);
						}
						aBuilder.Save(aFile.Name);
					}
				}

				using (FileStream stream = new FileStream(filename, FileMode.Create))
				{
					using (ZipFile zipFile = new ZipFile())
					{
						zipFile.CompressionLevel = ArgusLib.Zlib.CompressionLevel.BestCompression;
						zipFile.AddDirectory(assemblyDir.Path);
						zipFile.Save(stream);
					}
				}
			}
		}

		string GetModuleName(string resName, string cultureName)
		{
			int index = resName.LastIndexOf(".resources");
			if (index < 0)
				return cultureName + resName;
			return resName.Insert(index, '.' + cultureName);
		}

		Assembly GetAssemblyFromResourceName(string resourceName)
		{
			foreach (ResourceInfo ri in this.cbResources.Items)
			{
				if (ri.Name == resourceName)
					return ri.Assembly;
			}
			return null;
		}

		private void tsmiMenuHelpHomepage_Click(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start("https://localysator.codeplex.com/");
		}

		private void tsmiMenuHelpUpdates_Click(object sender, EventArgs e)
		{
			UpdateInfo.DownloadAsync(new Action<UpdateInfo>((updateInfo) =>
				{
					if (updateInfo == null || updateInfo.BuildVersion <= ArgusLib.Environment.GetAssemblyVersion())
					{
						if (sender != null)
							this.Invoke(new Action(() => { ArgusLib.Controls.MessageBox.Show("No updates available."); }));
					}
					else
						this.Invoke(new Action(() => { ArgusLib.Controls.UpdateInfoBox.Show(updateInfo); }));
				}));
		}
	}

	class ResourceInfo
	{
		public string Name { get; set; }
		public Assembly Assembly { get; set; }

		public ResourceInfo(string name, Assembly assembly)
		{
			this.Name = name;
			this.Assembly = assembly;
		}
	}
}
